﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;

namespace OpenESSOConfigurator.BusinessLayer.Atoms
{
    public class Password
    {
        #region private attributes
        private String _pwd;
        private PasswordPolicy _pwdPolicy;
        private String _confirmPwd;
        private String _newPwd;
        private String _confirmNewPwd;
        private String _oldPwd1;
        private String _oldPwd2;
        private String _oldPwd3;
        private String _oldPwd4;
        private String _oldPwd5;
        private String _oldPwd6;
        static readonly string[] alphaChar = { "a", "b", "c", "d", "e", "f", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };
        static readonly string[] numericChar = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" };
        static readonly char[] alphabeticSequences = new char[] { '0', '1', '2' , '3', '4', '5', '6', '7', '8', '9',
                                                        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
                                                        'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
                                                        'v', 'w', 'x', 'y', 'z'
                                                      };
        static readonly char[][] keyBoardsequences = {
                                           // qwertyUsKeyBoard sequence
                                           new char[] { 'q', 'w', 'e' , 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\',
                                                         'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'',
                                                         'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
                                                         '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+'
                                                       }
                                           // qwertyUkKeyBoard sequence
                                           ,new char[] { 'q', 'w', 'e' , 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\',
                                                         'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'',
                                                         'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
                                                         '!', '\"', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+'
                                                       }
                                           // azertyKeyBoard sequence
                                           ,new char[] { 'a', 'z', 'e' , 'r', 't', 'y', 'u', 'i', 'o', 'p', '-', '\\',
                                                         'q', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'',
                                                         'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
                                                         '&', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '+'
                                                       }
                                           };
        #endregion

        #region Getter & Setter
        public String Pwd
        {
            get { return _pwd; }
            set { _pwd = value; }
        }
        public PasswordPolicy PwdPolicy
        {
            get { return _pwdPolicy; }
            set { _pwdPolicy = value; }
        }
        public String ConfirmPwd
        {
            get { return _confirmPwd; }
            set { _confirmPwd = value; }
        }
        public String NewPwd
        {
            get { return _newPwd; }
            set { _newPwd = value; }
        }
        public String OldPwd1
        {
            get { return _oldPwd1; }
            set { _oldPwd1 = value; }
        }
        public String OldPwd2
        {
            get { return _oldPwd2; }
            set { _oldPwd2 = value; }
        }
        public String OldPwd3
        {
            get { return _oldPwd3; }
            set { _oldPwd3 = value; }
        }
        public String OldPwd4
        {
            get { return _oldPwd4; }
            set { _oldPwd4 = value; }
        }
        public String OldPwd5
        {
            get { return _oldPwd5; }
            set { _oldPwd5 = value; }
        }
        public String OldPwd6
        {
            get { return _oldPwd6; }
            set { _oldPwd6 = value; }
        }
        #endregion

        #region Public API
        /// <summary>
        /// Password strength indicator
        /// </summary>
        public enum PasswordStrengthIndex
        {
            /// <summary>
            /// Password has no strength - considered insecure (0/4)
            /// </summary>
            None = 0,
            /// <summary>
            /// Password is considered weak (1/4)
            /// </summary>
            Weak = 1,
            /// <summary>
            /// Password is medium strength (2/4)
            /// </summary>
            Medium = 2,
            /// <summary>
            /// Password is strong (3/4)
            /// </summary>
            Strong = 3,
            /// <summary>
            /// Password is strongest set (4/4)
            /// </summary>
            MostStrong = 4,
        }

        public Password ()
        {
        
        }
        public Password(String password,PasswordPolicy passwordPolicy)
        {
            PwdPolicy = passwordPolicy;
            Pwd = password;
        }
        public bool IsMinLenghtRespected()
        {
            if (this.Pwd.Length >= this.PwdPolicy.Lenght.MinValue)
                return true;
            else
                return false;
        }
        public bool IsMaxLenghtRespected()
        {
            if (this.Pwd.Length <= this.PwdPolicy.Lenght.MaxValue)
                return true;
            else
                return false;
        }
        // Occurence
        public int NumberOfUpperCase()
        {
            int FI = 0;
            int LI = 0;
            int counts = 0;
            for (int i = 0; i < alphaChar.Length; i++)
            {
                FI = Pwd.IndexOf(alphaChar[i].ToUpper());
                while (FI > -1)
                {
                    counts++;
                    LI = FI + 1;
                    FI = Pwd.IndexOf(alphaChar[i].ToUpper(), LI);
                }
            }
            return counts;
        }
        public int NumberOfLowerCase()
        {
            int FI = 0;
            int LI = 0;
            int counts = 0;
            for (int i = 0; i < alphaChar.Length; i++)
            {
                FI = Pwd.IndexOf(alphaChar[i]);
                while (FI > -1)
                {
                    counts++;
                    LI = FI + 1;
                    FI = Pwd.IndexOf(alphaChar[i], LI);
                }
            }
            return counts;
        }
        public int NumberOfNumeric()
        {
            int FI = 0;
            int LI = 0;
            int counts = 0;
            for (int i = 0; i < numericChar.Length; i++)
            {
                FI = Pwd.IndexOf(numericChar[i]);
                while (FI > -1)
                {
                    counts++;
                    LI = FI + 1;
                    FI = Pwd.IndexOf(numericChar[i], LI);
                }
            }
            return counts;
        }
        //UpperCase
        public bool CheckUpperCase()
        {
            Regex rgx = new Regex(@"[A-Z]");
            return rgx.IsMatch(Pwd);
        }
        public bool IsUpperCaseStartPwd()
        {
            if (Pwd.Length > 0)
            {
                for (int i = 0; i < alphaChar.Length; i++)
                {
                    if (Pwd.Substring(0, 1).Equals(alphaChar[i].ToUpper()))
                        return true;
                }
                return false;
            }
            else return false;
        }
        public bool IsMinUpperCaseRespected()
        {
            if (NumberOfUpperCase() >= this.PwdPolicy.UpperCase.MinValue)
                return true;
            else return false;
        }
        public bool IsMaxUpperCaseRespected()
        {
            if (NumberOfUpperCase() <= this.PwdPolicy.UpperCase.MaxValue)
                return true;
            else return false;
        }
        //LowerCase
        public bool CheckLowerCase()
        {
            Regex rgx = new Regex(@"[a-z]");
            return rgx.IsMatch(Pwd);
        }
        public bool IsLowerCaseStartPwd()
        {
            if (Pwd.Length > 0)
            {
                for (int i = 0; i < alphaChar.Length; i++)
                {
                    if (Pwd.Substring(0, 1).Equals(alphaChar[i]))
                        return true;
                }
                return false;
            }
            else return false;
        }
        public bool IsMinLowerCaseRespected()
        {
            if (NumberOfLowerCase() >= this.PwdPolicy.LowerCase.MinValue)
                return true;
            else return false;
        }
        public bool IsMaxLowerCaseRespected()
        {
            if (NumberOfLowerCase() <= this.PwdPolicy.LowerCase.MaxValue)
                return true;
            else return false;
        }
        //Numeric
        public bool CheckNumeric()
        {
            Regex rgx = new Regex(@"\d");
            return rgx.IsMatch(Pwd);
        }
        public bool IsNumericStartPwd()
        {
            if (Pwd.Length > 0)
            {
                for (int i = 0; i < numericChar.Length; i++)
                {
                    if (Pwd.Substring(0, 1).Equals(numericChar[i]))
                        return true;
                }
                return false;
            }
            else return false;
        }
        public bool IsMinNumericRespected()
        {
            if (NumberOfNumeric() >= this.PwdPolicy.Numeric.MinValue)
                return true;
            else return false;
        }
        public bool IsMaxNumericRespected()
        {
            if (NumberOfNumeric() <= this.PwdPolicy.Numeric.MaxValue)
                return true;
            else return false;
        }
        //Specialchar
        public bool CheckSpecialChar()
        {
            Regex rgx = new Regex(@"\W");
            return rgx.IsMatch(Pwd);
        }
        public bool IsSpecialCharStartPwd()
        {
            Regex rgx = new Regex(@"^\W");
            return rgx.IsMatch(Pwd);
        }
        public bool IsMinSpecialCharRespected()
        {
            Regex rgx = new Regex(@"\W{" + this.PwdPolicy.SpecialChar.MinValue.ToString() + ",}");
            return rgx.IsMatch(Pwd);
        }
        public bool IsMaxSpecialCharRespected()
        {
            Regex rgx = new Regex(@"\W{1," + (this.PwdPolicy.SpecialChar.MaxValue + 1).ToString() + "}");
            return rgx.IsMatch(Pwd);
        }
        public bool IsOnlyCharRespected()
        {
            if (this.PwdPolicy.SpecialChar.OnlyCharValue.Length > 0)
            {
                Regex rgx = new Regex(@"\W");
                foreach (Match match in rgx.Matches(Pwd))
                { 
                    if(!this.PwdPolicy.SpecialChar.OnlyCharValue.Contains(match.ToString()))
                        return false;
                }
               return true; 
            }
            return true;
        }
        public bool IsExcludedCharRespected()
        {
            if (this.PwdPolicy.SpecialChar.ExcludedCharValue.Length > 0)
            {
                Regex rgx = new Regex(@"\W");
                foreach (Match match in rgx.Matches(Pwd))
                {
                    if (this.PwdPolicy.SpecialChar.ExcludedCharValue.Contains(match.ToString()))
                        return false;
                }
                return true;
            }
            return true;
        }
        public bool IsNotEqualToPreviousPassword()
        {
            ArrayList passwordToCheck =new ArrayList(6);
            if(OldPwd1.Length>0)
            passwordToCheck.Add(OldPwd1);
            if (OldPwd2.Length > 0)
            passwordToCheck.Add(OldPwd2);
            if (OldPwd3.Length > 0)
            passwordToCheck.Add(OldPwd3);
            if (OldPwd4.Length > 0)
            passwordToCheck.Add(OldPwd4);
            if (OldPwd5.Length > 0)
            passwordToCheck.Add(OldPwd5);
            if (OldPwd6.Length > 0)
            passwordToCheck.Add(OldPwd6);
            for (int i = 0; i <= this.PwdPolicy.PreviousPassword.Number - 1; i++)
            { 
                if(Pwd.Equals(passwordToCheck[i]))
                    return false;
            }
            return true;
        }
        public bool ContainsAlphabeticSequences()
        {
            int maxLength = Pwd.Length;
            int calcLength;
            calcLength = CheckSequence(alphabeticSequences);
            if (calcLength < maxLength)
            {
                return true;
            }
            else return false;
        }
        public bool ContainsKeyBoardSequences()
        {
            int maxLength = Pwd.Length;
            int calcLength;
            foreach (char[] sequence in keyBoardsequences)
            {
                calcLength = CheckSequence(sequence);
                if (calcLength < maxLength)
                {
                    return true;
                }
            }
            return false;
        }
        public bool IsDictionaryItem()
        {
            if (this.PwdPolicy.Dictionary.Items.Length > 0)
            {
                String[] dictionaryItems = this.PwdPolicy.Dictionary.Items.Split(';');
                for (int i = 0; i < dictionaryItems.Length; i++)
                {
                    if (Pwd.Equals(dictionaryItems[i]))
                        return true;
                }
                return false;
            }
            else
                return false; 
        }
        /// <summary>
        /// Given a password it attempts to determine its strength based on entropy and effective length
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns>Strength indicator</returns>
        /// <remarks>
        /// Strength is ultimately deceided by the object
        /// implementing the password processors
        /// </remarks>
        public PasswordStrengthIndex GetPasswordStrength()
        {
            int bitStrength = GetPasswordPseudoEntropy();

            if (bitStrength < 10)
            {
                return PasswordStrengthIndex.None;
            }

            if (bitStrength < 30)
            {
                return PasswordStrengthIndex.Weak;
            }

            if (bitStrength < 40)
            {
                return PasswordStrengthIndex.Medium;
            }

            if (bitStrength < 60)
            {
                return PasswordStrengthIndex.Strong;
            }

            return PasswordStrengthIndex.MostStrong;
        }

        public bool IsPasswordRespectPolicy()
        {
                if(PwdPolicy.Lenght.CheckMin)
                {
                    if(!IsMinLenghtRespected())
                    return false;
                }
                if(PwdPolicy.Lenght.CheckMax)
                {
                    if(!IsMaxLenghtRespected())
                    return false;
                }
                if(!PwdPolicy.UpperCase.Allow)
                {
                    if(CheckUpperCase())
                    return false;
                }
                if(PwdPolicy.UpperCase.MustStart)
                {
                    if(!IsUpperCaseStartPwd())
                    return false;
                }
                if(PwdPolicy.UpperCase.CheckMin)
                {
                    if(!IsMinUpperCaseRespected())
                    return false;
                }
                if(PwdPolicy.UpperCase.CheckMax)
                {
                    if(!IsMaxUpperCaseRespected())
                    return false;
                }
                //lower
                if(!PwdPolicy.LowerCase.Allow)
                {
                    if(CheckLowerCase())
                    return false;
                }
                if(PwdPolicy.LowerCase.MustStart)
                {
                    if(!IsLowerCaseStartPwd())
                    return false;
                }
                if(PwdPolicy.LowerCase.CheckMin)
                {
                    if(!IsMinLowerCaseRespected())
                    return false;
                }
                if (PwdPolicy.LowerCase.CheckMax)
                {
                    if(!IsMaxLowerCaseRespected())
                    return false;
                }
                //Numeric
                if (!PwdPolicy.Numeric.Allow)
                {
                    if(CheckNumeric())
                    return false;
                }
                if(PwdPolicy.Numeric.MustStart)
                {
                    if(!IsNumericStartPwd())
                    return false;
                }
                if(PwdPolicy.Numeric.CheckMin)
                {
                    if(!IsMinNumericRespected())
                    return false;
                }
                if(PwdPolicy.Numeric.CheckMax)
                {
                    if(!IsMaxNumericRespected())
                    return false;
                }
                //Special Char
                 if(!PwdPolicy.SpecialChar.Allow)
                {
                    if(CheckSpecialChar())
                    return false;
                }
                if(PwdPolicy.SpecialChar.MustStart)
                {
                    if(!IsSpecialCharStartPwd())
                    return false;
                }
                if(PwdPolicy.SpecialChar.CheckMin)
                {
                    if(!IsMinSpecialCharRespected())
                    return false;
                }
                if(PwdPolicy.SpecialChar.CheckMax)
                {
                    if(!IsMaxSpecialCharRespected())
                    return false;
                }

                if(PwdPolicy.SpecialChar.OnlyChar)
                {
                    if(!IsOnlyCharRespected())
                    return false;
                }
                if(PwdPolicy.SpecialChar.ExcludedChar)
                {
                    if(!IsExcludedCharRespected())
                    return false;
                }
                if(PwdPolicy.Sequence.CheckAlphaSequence)
                {
                    if(ContainsAlphabeticSequences())
                    return false;
                }
                if(PwdPolicy.Sequence.CheckKeyBoardSequence)
                {
                    if(ContainsKeyBoardSequences())
                    return false;
                }
                if(PwdPolicy.Dictionary.Check)
                {
                    if(IsDictionaryItem())
                    return false;
                }
                return true;
        }

        #endregion

        #region Private API
        /// <summary>
        /// Returns length of password after sequences have been removed
        /// </summary>
        /// <param name="sequenceArray">The sequence array.</param>
        /// <returns></returns>
        private int CheckSequence(char[] sequenceArray)
        {
            int sequenceCounter = 0;
            int lastIndex = -2;
            int sequenceFalsePositiveSupressionThreshold = 1;
            int sequenceSupressionCounter = 0;

            foreach (char character in Pwd.ToLower())
            {
                int indexInArray = Array.IndexOf<char>(sequenceArray, character);

                if ((indexInArray > -1) && ((indexInArray == lastIndex)
                                             || (indexInArray == lastIndex + 1)
                                             || (indexInArray == lastIndex - 1))
                   )
                {
                    // Only count the information as being sequenced if we have at least 3
                    // characters as being in sequence. Without this safe check we cause
                    // problems with longer passwords having accidental sequences triggering
                    // a failure in policies
                    if (sequenceSupressionCounter == sequenceFalsePositiveSupressionThreshold)
                    {
                        sequenceCounter += sequenceFalsePositiveSupressionThreshold + 1;
                    }
                    if (sequenceSupressionCounter > sequenceFalsePositiveSupressionThreshold)
                    {
                        sequenceCounter++;
                    }
                    sequenceSupressionCounter++;
                }
                else
                {
                    sequenceSupressionCounter = 0;
                }

                lastIndex = indexInArray;
            }

            return Pwd.Length - sequenceCounter;
        }
        /// <summary>
        /// Returns an approximation to the entropy for the supplied password
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns>Calculated strength of password in bits</returns>
        /// <remarks>
        /// Note as this is relatively subjective (unless the password is random)
        /// so it rarely will make sense to compare values between different
        /// password strength providers
        /// </remarks>
        private int GetPasswordPseudoEntropy()
        {
            int alpha = Pwd.Length;
            alpha = this.CheckSequence(alphabeticSequences);
            int keyBord = Pwd.Length;
            keyBord = this.CheckSequence(alphabeticSequences);
            
            // First get the length of the password, correcting for any sequences
            int getMiniumNonSequencedLength = Math.Min(alpha,keyBord) ;
            int maxDictionaryMatch = 0;
            maxDictionaryMatch = LongestMatchingDictionaryEntry();

            // Determine the max entropy for each character
            double entropyPerCharacter = GetPasswordEntropyPerCharacter();

            // return the strength removing any sequences and the biggest dictionary word
            return Convert.ToInt32(entropyPerCharacter * (getMiniumNonSequencedLength - maxDictionaryMatch));
        }
        /// <summary>
        /// Gets the password entropy per character.
        /// </summary>
        /// <param name="password">The password to tbe determined.</param>
        /// <returns>Max number of entropy bits per character in the password</returns>
        private double GetPasswordEntropyPerCharacter()
        {
            int maxCombinationsPerCharacterInPassword = 0;
            bool lowerCaseCharactersDetected = false;
            bool upperCaseCharactersDetected = false;
            bool digitsDetected = false;
            bool specialCharactersDetected = false;

            foreach (char character in Pwd.ToCharArray())
            {
                lowerCaseCharactersDetected |= char.IsLower(character);
                upperCaseCharactersDetected |= char.IsUpper(character);
                digitsDetected |= char.IsNumber(character);
                specialCharactersDetected |= (!char.IsLetter(character) && !char.IsNumber(character));
            }

            if (lowerCaseCharactersDetected) maxCombinationsPerCharacterInPassword += 26;
            if (upperCaseCharactersDetected) maxCombinationsPerCharacterInPassword += 26;
            if (digitsDetected) maxCombinationsPerCharacterInPassword += 10;
            if (specialCharactersDetected) maxCombinationsPerCharacterInPassword += 32;

            return Math.Log(maxCombinationsPerCharacterInPassword, 2);
        }
        /// <summary>
        /// Checks password for dictionary entries
        /// </summary>
        /// <param name="password">The password to check.</param>
        /// <returns>Max length of matching dictionary entry</returns>
        private int LongestMatchingDictionaryEntry()
        {
            int maxDictionaryMatch = 0;
            // Check length against any dictionary values. We record the longest match for any one word
            // we are not trying to remove contatenated words as this is both quite hard and the fact
            // is that contatenated dictionary words are harder to brute. So we will just stick to the
            // values found in the dictionary)
            foreach (string word in this.PwdPolicy.Dictionary.Items.Split(';'))
            {
                if (Pwd.Contains(word))
                {
                    if (word.Length > maxDictionaryMatch)
                    {
                        maxDictionaryMatch = word.Length;
                    }
                }
            }
            return maxDictionaryMatch;
        }
        private string Simple1337Decode(string password)
        {
            string deLeeted = password.Replace('@', 'a')
                //.Replace('6', 'b')
                                      .Replace('8', 'b')
                                      .Replace('[', 'c')
                                      .Replace('(', 'c')
                                      .Replace('3', 'e')
                                      .Replace('6', 'g')
                                      .Replace('9', 'g')
                                      .Replace('#', 'h')
                                      .Replace('!', 'i')
                                      .Replace('1', 'i')
                                      .Replace('1', 'l')
                                      .Replace(@"/\/\", "m")
                                      .Replace(@"|\/|", "m")
                                      .Replace(@"(\/)", "m")
                                      .Replace(@"|\|", "n")
                                      .Replace('0', 'o')
                                      .Replace('5', 's')
                                      .Replace('$', 's')
                                      .Replace('7', 't')
                                      .Replace('+', 't')
                                      .Replace(@"\/\/", "w")
                                      .Replace(@"\/", "v")
                                      .Replace('2', 'z')
                                      ;

            return deLeeted;
        }
        #endregion

    }
}
